Elixir 中有兩種函式,具名函式及匿名函式。我們先從具名函式的語法開始:
def add_one(x) do
x + 1
end
寫過 Ruby 的人會覺得超親切,只多了一個 do
而己。而函式名稱的慣例也跟 Ruby 一樣,會用 snake_case,也就是全小寫加底線命名。
但如本節標題所說,有名的總是比較麻煩一點。Elixir 的具名函式一定要定義在 module (模組)裡。module 簡單來說就是用 namespace 來將函式分組,避免過多的同名函式造成預期之外的行為。而 module 的命名則會用大寫開頭的 CamelCase。
順便趁這機會,一步步帶大家來編寫及執行 elixir 檔案裡的函式。
math.exs
檔案defmodule Math do
def add_one(x) do
x + 1
end
end
y = Math.add_one(10)
IO.inspect(y)
elixir math.exs
,可以看到輸出 11
注意 elixir 的語法中, do...end
是一組的。只要有 end
,就一定會配上 do
。慣例上會用兩個空白縮排,目前正在 rc 的 elixir 1.6 版將會內建排版功能,這個也會在後面的章節討論。
我們在檔案的一開始用 defmodule
定義了 Math
這個模組,這模組裡目前只定義了接收一個參數的 add_one
這個函式。
在模組定義之外,我們用 Math.add_one()
呼叫了函式,且傳入參數 10
。將結果綁定到 y
變數上。最後用 IO.inspect
將它印出來。
IO
是 elixir 裡處理輸出輸入的模組,裡面的 IO.inspect
函式是用來監看詳細的資料結構用,我們在之後的說明裡會一直使用它,目前只要知道這樣就夠了。
Elixir 的函式有個更棒的特性。當你在定義 elixir 函式需要的參數 (x)
時,並不是單純的說: 這函式需要一個參數,傳進來,我會把它叫做 x。昨天我們曾提到,elixir 用單變數去 match 任何值都會成功。而 elixir 函式定義裡的參數部份,也是個 patterh matching。我們來把剛剛定義的函式上方加一個同名的函式來試試看:
defmodule Math do
def add_one(0) do
"I don't like zero..."
end
# 原先的 def add_one(x) do...
end
z = Math.add_one(0)
IO.inspect z
#=> "I don't like zero"
Elixir 中,函式的 pattern matching 是由上往下,找到能夠成功 pattern matching 參數列的函式,就呼叫該段函式。
如果函式本體非常短,用 do...end
的語法得要佔三行,可以改用底下這個縮寫語法:
def add_one(x), do: x + 1
也就是在函式的參數括號後方加個逗點及空白 ,
,然後 do
的後方加個冒號及空白:
,就可以省略掉 end
,然後放在同一行。
所謂的遞迴,是指函式在執行的過程中呼叫自己。我們來用遞迴寫一個 Math.sum
函式來加總一個串列:
def sum([]), do: 0
def sum([h | t]), do: h + sum(t)
不要懷疑,這就是完整的程式。先看第二行,sum
會將串列的頭尾切開,用剩下串列的尾巴再一次傳進 sum 裡呼叫。再將結果跟首值相加。這個呼叫自己的行為會一直進行下去,直到串列裡沒有任何值為止。而我們用第一個函式告訴它,如果遇到空的串列,那就回傳 0
。
因為是第一次,我們試著扮演一下 elixir VM,用文字來一步步跑這段 code。假設我們接到了某個人類呼叫 sum([1, 2, 3])
,開始往下走吧:
[1, 2, 3]
不是空的,所以跳過第一行。[1 | [2, 3]]
,所以進入函式本體,綁定 h = 1
與 t = [2,3]
。(1 + sum([2, 3]))
。 這樣是沒辦法加的,我們接著對加號後面那個 sum
求值。[2, 3]
也不是空的,所以跳過第一個,進入第二個函式本體,切成 [2 | [3]]
並綁定變數。(1 + (2 + sum([3])))
。這依然不知道怎麼加,繼續對裡面那個 sum([3])
求值。[3]
依舊不是空串列,跳過第一行。在第二行裡,會被切成… [3 | []]
。(1 + (2 + ( 3 + sum([]))))
。還是不會加,再來求值吧。sum([])
要回傳 0
,不囉嗦立刻把值丟回去。(1 + (2 + (3 + 0)))
囉!這樣就是小學數學了,由內而外加出去,就得到結果 6
了,結案!假裝成 VM 實在是太累了,今天就先這樣吧。本日我們學到的東西是:
def name do...end
,或是 def name(), do:
來定義defmodule MyModule
。elixir 檔名
來執行檔案。IO.inspect
可以印東西出來偷看。這個遞迴看起來運作的很好,但是其實它有那麼一點點的問題…我們明天要來討論問題在哪,以及如何解決。
Happy hacking! 明天見。
原來全小寫加底線命名叫做snake_case,長知識
XD 其實就叫 snake case,只是順便展示形狀。